﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom;
using System.Xml;
using System.ComponentModel;
using System.CodeDom.Compiler;
using System.IO;

namespace RBTools.CodeGenerator.BuildProviders
{
    public class MessageCodeGenerator 
    {
        #region Ctors

        public MessageCodeGenerator()
        {
            DefaultNamespace = "Messagess";
        }

        #endregion

        #region Properties

        public string DefaultNamespace
        {
            get;
            set;
        }

        #endregion

        #region Methods

        public CodeCompileUnit BuildCodeCompileUnit(XmlDocument xmlDoc)
        {
            CodeCompileUnit codeCompileUnit = new CodeCompileUnit();

            CodeNamespace codeNamespace = new CodeNamespace(DefaultNamespace);

            codeNamespace.Imports.Add(new CodeNamespaceImport("System"));
            codeNamespace.Imports.Add(new CodeNamespaceImport("System.Text"));

            codeCompileUnit.Namespaces.Add(codeNamespace);


            //Generate Message Class
            CodeTypeDeclaration messageTypeDeclaration = GenerateMessageEntryClass(xmlDoc);
            codeNamespace.Types.Add(messageTypeDeclaration);

            //Generate Messages Class
            CodeTypeDeclaration messagesTypeDeclaration = GenerateCategoryCalss(xmlDoc);
            codeNamespace.Types.Add(messagesTypeDeclaration);

            return codeCompileUnit;
        }

        private CodeTypeDeclaration GenerateMessageEntryClass(XmlDocument xmlDoc)
        {
            allPropertyNameMappings = new Dictionary<string, string[]>();

            var messageEntryElement = xmlDoc.GetElementsByTagName("messageEntry")[0];
            CodeTypeDeclaration messageEntryTypeDeclaration = new CodeTypeDeclaration(messageEntryElement.Attributes["className"].Value);

            CodeConstructor constructor = new CodeConstructor();
            constructor.Attributes = MemberAttributes.Public;
            constructor.Name = messageEntryElement.Attributes["className"].Value;
            var propertyElements = messageEntryElement.FirstChild.ChildNodes.Cast<XmlElement>();
            foreach (var propertyElement in propertyElements)
            {
                string propertyName = propertyElement.Attributes["name"].Value;
                string propertyType = propertyElement.Attributes["type"].Value;
                string mappingTo = propertyElement.Attributes["mappingto"].Value;
                string fieldName = "_" + propertyName;
                string parameterName = "__" + propertyName;

                allPropertyNameMappings.Add(propertyName, new string[] { mappingTo, propertyType });

                CodeMemberField codeField = new CodeMemberField(propertyType, fieldName);
                messageEntryTypeDeclaration.Members.Add(codeField);

                CodeFieldReferenceExpression codeFieldReference = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName);

                CodeMemberProperty codeProperty = new CodeMemberProperty();
                codeProperty.Name = propertyName;
                codeProperty.HasGet = true;
                codeProperty.HasSet = true;

                CodeMethodReturnStatement getPropertyReturn = new CodeMethodReturnStatement(codeFieldReference);
                codeProperty.GetStatements.Add(getPropertyReturn);
                CodeAssignStatement setPropertyAssign = new CodeAssignStatement(codeFieldReference,
                                    new CodePropertySetValueReferenceExpression());
                codeProperty.SetStatements.Add(setPropertyAssign);
                codeProperty.Type = new CodeTypeReference(propertyType);
                codeProperty.Attributes = MemberAttributes.Public;

                messageEntryTypeDeclaration.Members.Add(codeProperty);

                CodeAssignStatement codeAssign = new CodeAssignStatement();
                codeAssign.Left = codeFieldReference;
                codeAssign.Right = new CodeArgumentReferenceExpression(parameterName);
                constructor.Statements.Add(codeAssign);

                constructor.Parameters.Add(new CodeParameterDeclarationExpression(propertyType, parameterName));
            }

            messageEntryTypeDeclaration.Members.Add(constructor);

            return messageEntryTypeDeclaration;
        }

        private CodeTypeDeclaration GenerateCategoryCalss(XmlDocument xmlDoc)
        {
            CodeTypeDeclaration messagesTypeDeclaration = new CodeTypeDeclaration("Messages");

            var messageEntryClassName = xmlDoc.GetElementsByTagName("messageEntry")[0].Attributes["className"].Value;
            var messageElements = xmlDoc.GetElementsByTagName("message").Cast<XmlElement>();
            var categories = (from e in messageElements select e.Attributes["category"].Value).Distinct();
            foreach (var category in categories)
            {
                var categoryTypeDeclaration = new CodeTypeDeclaration(category);
                messagesTypeDeclaration.Members.Add(categoryTypeDeclaration);

                var messagesInTheCategory = messageElements.Where(e => e.GetAttribute("category") == category);
                foreach (var element in messagesInTheCategory)
                {
                    GenerateCategoryProperties(categoryTypeDeclaration, element, messageEntryClassName);
                }
            }
            return messagesTypeDeclaration;
        }

        private void GenerateCategoryProperties(CodeTypeDeclaration categoryTypeDeclaration, XmlElement messageElement, string messageEntryClassName)
        {
            string id = messageElement.GetAttribute("id");

            var categoryField = new CodeMemberField(messageEntryClassName, id);
            categoryField.Attributes = MemberAttributes.Static | MemberAttributes.Public;

            CodeObjectCreateExpression codeObjectCreateExp = new CodeObjectCreateExpression(messageEntryClassName);
            foreach (var kv in allPropertyNameMappings)
            {
                string propertyValueStr = messageElement.GetAttribute(allPropertyNameMappings[kv.Key][0]);
                Type propertyType = Type.GetType(allPropertyNameMappings[kv.Key][1]);
                TypeConverter converter = TypeDescriptor.GetConverter(propertyType);
                object propertyValue = converter.ConvertFromString(propertyValueStr);
                codeObjectCreateExp.Parameters.Add(new CodePrimitiveExpression(propertyValue));
            }
            categoryField.InitExpression = codeObjectCreateExp;
            categoryTypeDeclaration.Members.Add(categoryField);
        }

        #endregion

        
        #region Other Methods

        private Dictionary<string, string[]> allPropertyNameMappings = new Dictionary<string, string[]>();

        #endregion
    }
}
